/*
 * Copyright (c) [2005] [Jeffrey Moore]
 *
 * Redistributions in source code form must reproduce the above copyright and 
 * this condition.
 *
 * The contents of this file are subject to the Sun Project JXTA License 
 * Version 1.1 (the "License"); you may not use this file except in compliance 
 * with the License. A copy of the License is available at 
 * http://www.jxta.org/jxta_license.html.
 *
 */

/*
 * ViJxtaCallControl.java
 *
 * Created on April 7, 2005, 9:02 AM
 */

package net.jxta.myjxta.plugins.vijxta;

import net.jxta.endpoint.MessageElement;
import net.jxta.endpoint.StringMessageElement;
import net.jxta.logging.Logging;
import net.jxta.myjxta.MyJXTA;
import net.jxta.myjxta.View;
import net.jxta.myjxta.dialog.Dialog;
import net.jxta.myjxta.dialog.DialogMessage;

import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class acts as a controller for a video session. Much of this class's
 * protocol handling is adapted from VoJxtaCallControl.
 *
 * @author jamoore
 */
@SuppressWarnings({"UnnecessaryUnboxing", "UnnecessaryBoxing"})
public final class ViJxtaCallControl implements DeviceMonitorControl.DeviceErrorListener {

    /**
     * Time to wait for response after a command  message is sent
     * out. Default is 10 seconds.
     */
    private static final long DEFAULT_MESSAGE_ACK_TIMEOUT = 60000;

    /**
     * Minimum time we will wait till a message ack comes back
     */
    private static final long MINIMUM_MESSAGE_ACK_TIMEOUT = 2000;
    /**
     * Timeout waiting for config request
     */
    private static final long CONFIG_WAIT_TIMOUT = 20000;

    /**
     * Tag for an element element carrying video data
     */
    public static final String TAG_IMAGE_DATA = "ImageData";

    /**
     * Element tag denoting a message containing vijxta session command data
     */
    public static final String TAG_SESSION_COMMAND = "ViJxtaSessionCommand";

    public static final String TAG_IMAGE_QUALITY_ELEMENT = "ViJxtaImageQuality";

    public static final String TAG_IMAGE_FORMAT_SIZE_ELEMENT = "ViJxtaFormatSize";

    public static final String TAG_IMAGE_FORMAT_TYPE_ELEMENT = "ViJxtaFormatType";

    public static final String TAG_IMAGE_TRANSMIT_ELEMENT = "ViJxtaTransmit";

    public static final String TAG_IMAGE_RECEIVE_ELEMENT = "ViJxtaReceive";

    public static final String TAG_ROUND_TRIP_REQUEST_TIME = "RoundTripRequestTime";

    /**
     * commands sent btn peers managing the vijxta session
     */

    public static final String COMMAND_VIJXTA_INVITE_REQUEST = "ViJxtaInviteRequest";

    public static final String COMMAND_VIJXTA_INVITE_ACCEPT = "ViJxtaInviteAccept";

    public static final String COMMAND_VIJXTA_START_REQUEST = "ViJxtaStartRequest";

    public static final String COMMAND_VIJXTA_START_ACCEPT = "ViJxtaStartAccept";

    public static final String COMMAND_VIJXTA_CONFIG_REQUEST = "ViJxtaConfigRequest";

    public static final String COMMAND_VIJXTA_CONFIG_ACCEPT = "ViJxtaConfigAccept";

    public static final String COMMAND_VIJXTA_DATA = "ViJxtaData";

    public static final String COMMAND_VIJXTA_HOLD_REQUEST = "ViJxtaHoldRequest";

    public static final String COMMAND_VIJXTA_HOLD_ACCEPT = "ViJxtaHoldAccept";

    public static final String COMMAND_VIJXTA_RESUME_REQUEST = "ViJxtaResumeRequest";

    public static final String COMMAND_VIJXTA_RESUME_ACCEPT = "ViJxtaResumeAccept";

    public static final String COMMAND_VIJXTA_HANGUP_REQUEST = "ViJxtaHangUpRequest";

    public static final String COMMAND_VIJXTA_HANGUP_ACCEPT = "ViJxtaHangUpAccept";

    public static final String COMMAND_VIJXTA_DISCONNECT_REQUEST = "ViJxtaDisconnectRequest";

    public static final String COMMAND_VIJXTA_DISCONNECT_ACCEPT = "ViJxtaDisconnectAccept";

    public static final String COMMAND_VIJXTA_ROUND_TRIP_REQUEST = "ViJxtaRoundTripRequest";

    public static final String COMMAND_VIJXTA_ROUND_TRIP_ACCEPT = "ViJxtaRoundTripAccept";


    /**
     * internal session state
     */


    public static final int SESSION_VIJXTA_DISCONNECTED = 10;

    public static final int SESSION_VIJXTA_DISCONNECTING = 11;

    public static final int SESSION_VIJXTA_CONNECTED = 12;

    public static final int SESSION_VIJXTA_CONNECTING = 13;

    public static final int SESSION_VIJXTA_STARTING = 20;

    public static final int SESSION_VIJXTA_STARTED = 30;

    public static final int SESSION_VIJXTA_ENDING = 40;

    public static final int SESSION_VIJXTA_ENDED = 50;

    public static final int SESSION_VIJXTA_INCALL = 100;

    public static final int SESSION_VIJXTA_HOLDING = 110;

    public static final int SESSION_VIJXTA_DISCONNECT_REQUEST_SENT = 210;

    public static final int SESSION_VIJXTA_DISCONNECT_REQUEST_RECEIVED = 220;

    public static final int SESSION_VIJXTA_DISCONNECT_ACCEPT_SENT = 230;

    public static final int SESSION_VIJXTA_DISCONNECT_ACCEPT_RECEIVED = 240;

    public static final int SESSION_VIJXTA_RESUME_REQUEST_SENT = 250;

    public static final int SESSION_VIJXTA_RESUME_REQUEST_RECEIVED = 260;

    public static final int SESSION_VIJXTA_RESUME_ACCEPT_SENT = 270;

    public static final int SESSION_VIJXTA_RESUME_ACCEPT_RECEIVED = 280;

    public static final int SESSION_VIJXTA_HOLD_ACCEPT_SENT = 290;

    public static final int SESSION_VIJXTA_HOLD_ACCEPT_RECEIVED = 310;

    public static final int SESSION_VIJXTA_HOLD_REQUEST_SENT = 320;

    public static final int SESSION_VIJXTA_HOLD_REQUEST_RECEIVED = 330;

    public static final int SESSION_VIJXTA_START_REQUEST_SENT = 340;

    public static final int SESSION_VIJXTA_START_REQUEST_RECEIVED = 350;

    public static final int SESSION_VIJXTA_START_ACCEPT_SENT = 360;

    public static final int SESSION_VIJXTA_START_ACCEPT_RECEIVED = 370;

    public static final int SESSION_VIJXTA_CONFIG_REQUEST_SENT = 470;

    public static final int SESSION_VIJXTA_CONFIG_REQUEST_RECEIVED = 480;

    public static final int SESSION_VIJXTA_CONFIG_ACCEPT_SENT = 490;

    public static final int SESSION_VIJXTA_CONFIG_ACCEPT_RECEIVED = 500;

    public static final int SESSION_VIJXTA_HANGUP_ACCEPT_RECEIVED = 380;

    public static final int SESSION_VIJXTA_HANGUP_ACCEPT_SENT = 390;

    public static final int SESSION_VIJXTA_HANGUP_REQUEST_RECEIVED = 410;

    public static final int SESSION_VIJXTA_HANGUP_REQUEST_SENT = 420;

    public static final int SESSION_VIJXTA_INVITE_REQUEST_SENT = 430;

    public static final int SESSION_VIJXTA_INVITE_REQUEST_RECEIVED = 440;

    public static final int SESSION_VIJXTA_INVITE_ACCEPT_SENT = 450;

    public static final int SESSION_VIJXTA_INVITE_ACCEPT_RECEIVED = 460;

    public int protocolState = SESSION_VIJXTA_DISCONNECTED;

    public static final int MINIMUM_QUALITY = 0;

    public static final int MAXIMUM_QUALITY = 10;

    public static final int DEFAULT_QUALITY = 1;

    public static final long SECOND = 1000;

    public static final long MINUTE = SECOND * 60;

    public static final long HOUR = MINUTE * 60;

    public static final String TIME_SEPARATOR = ":";

    private long messageAckTimeout = DEFAULT_MESSAGE_ACK_TIMEOUT;

    public static final int NO_DEVICE = 0;

    public static final int LOCAL_DEVICE = 1;

    public static final int NETWORKED_DEVICE = 2;

    private int localVideoSourceType = NO_DEVICE;

    private long roundTrip = 0;

    private Dialog viJxtaDialog = null;

    private View myJxtaView = null;

    private MyJXTA myJxtaController = null;

    private DialogMessage templateMessage = null;

    private ViJxtaDialogView viJxtaView = null;

    private static final int PIPE_CONNECT_SLEEP_TIME = 200;

    private boolean sessionPersisted = false;

    protected boolean remotePeerReceive = false;

    protected boolean remotePeerTransmit = false;

    private String sessionFileName = null;

    static final Logger LOG = Logger.getLogger(ViJxtaCallControl.class.getName());

    private long callStartTime = 0;

    private long callEndTime = 0;

    private long callElapsedTime = 0;


    private RemoteMonitorControl remoteMonitorControl = null;


    private DeviceMonitorControl localMonitorControl = null;

    private boolean locallyInitiated = false;

    private ReceivedMessageTimeoutThread receiveTimeoutThread = null;

    /**
     * started on command message dispatch. waits for ack to message (notofy)
     * then dies
     */
    private MessageAckThread messageAckThread = null;

    private MyLock configStateLock = null;

    private Hashtable<Integer, String> sessionStateTable = null;

    private Timer generalTimer = null;

    private final long ROUND_TRIP_INTERVAL = 1000; // in ms

    private TimerTask roundTripTimerTask = null;

    private boolean isJMFPresent = false;

    private String errorMessage = null;

    private final boolean configMessageReceived = false;

    private TimerTask configWaitTimerTask = null;

    private boolean configured = false;

    /**
     * Creates a new instance of VijxtaCallControl
     */
    public ViJxtaCallControl(ViJxtaDialogView vijxtaView, View p_view, Dialog dialog, boolean locallyInitiated) {

        LOG.setLevel(Level.INFO);

        this.viJxtaView = vijxtaView;

        this.viJxtaDialog = dialog;

        this.locallyInitiated = locallyInitiated;

        this.configStateLock = new MyLock();

        this.configStateLock.setLocked(true);

        this.sessionStateTable = new Hashtable<Integer, String>();
        sessionStateTable.put(new Integer(SESSION_VIJXTA_CONFIG_ACCEPT_RECEIVED), "SESSION_VIJXTA_CONFIG_ACCEPT_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_CONFIG_ACCEPT_SENT), "SESSION_VIJXTA_CONFIG_ACCEPT_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_CONFIG_REQUEST_RECEIVED), "SESSION_VIJXTA_CONFIG_REQUEST_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_CONFIG_REQUEST_SENT), "SESSION_VIJXTA_CONFIG_REQUEST_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_CONNECTED), "SESSION_VIJXTA_CONNECTED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_CONNECTING), "SESSION_VIJXTA_CONNECTING");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_DISCONNECTED), "SESSION_VIJXTA_DISCONNECTED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_DISCONNECTING), "SESSION_VIJXTA_DISCONNECTING");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_DISCONNECT_ACCEPT_RECEIVED), "SESSION_VIJXTA_DISCONNECT_ACCEPT_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_DISCONNECT_ACCEPT_SENT), "SESSION_VIJXTA_DISCONNECT_ACCEPT_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_DISCONNECT_REQUEST_RECEIVED), "SESSION_VIJXTA_DISCONNECT_REQUEST_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_ENDED), "SESSION_VIJXTA_ENDED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_DISCONNECT_REQUEST_SENT), "SESSION_VIJXTA_DISCONNECT_REQUEST_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_ENDING), "SESSION_VIJXTA_ENDING");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_HANGUP_ACCEPT_RECEIVED), "SESSION_VIJXTA_HANGUP_ACCEPT_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_HANGUP_ACCEPT_SENT), "SESSION_VIJXTA_HANGUP_ACCEPT_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_HANGUP_REQUEST_RECEIVED), "SESSION_VIJXTA_HANGUP_REQUEST_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_HANGUP_REQUEST_SENT), "SESSION_VIJXTA_HANGUP_REQUEST_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_HOLDING), "SESSION_VIJXTA_HOLDING");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_HOLD_ACCEPT_RECEIVED), "SESSION_VIJXTA_HOLD_ACCEPT_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_HOLD_ACCEPT_SENT), "SESSION_VIJXTA_HOLD_ACCEPT_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_HOLD_REQUEST_RECEIVED), "SESSION_VIJXTA_HOLD_REQUEST_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_HOLD_REQUEST_SENT), "SESSION_VIJXTA_HOLD_REQUEST_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_INCALL), "SESSION_VIJXTA_INCALL");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_INVITE_ACCEPT_RECEIVED), "SESSION_VIJXTA_INVITE_ACCEPT_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_INVITE_ACCEPT_SENT), "SESSION_VIJXTA_INVITE_ACCEPT_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_INVITE_REQUEST_RECEIVED), "SESSION_VIJXTA_INVITE_REQUEST_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_INVITE_REQUEST_SENT), "SESSION_VIJXTA_INVITE_REQUEST_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_RESUME_ACCEPT_RECEIVED), "SESSION_VIJXTA_RESUME_ACCEPT_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_RESUME_ACCEPT_SENT), "SESSION_VIJXTA_RESUME_ACCEPT_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_RESUME_REQUEST_RECEIVED), "SESSION_VIJXTA_RESUME_REQUEST_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_RESUME_REQUEST_SENT), "SESSION_VIJXTA_RESUME_REQUEST_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_STARTED), "SESSION_VIJXTA_STARTED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_STARTING), "SESSION_VIJXTA_STARTING");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_START_ACCEPT_RECEIVED), "SESSION_VIJXTA_START_ACCEPT_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_START_ACCEPT_SENT), "SESSION_VIJXTA_START_ACCEPT_SENT");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_START_REQUEST_RECEIVED), "SESSION_VIJXTA_START_REQUEST_RECEIVED");
        sessionStateTable.put(new Integer(SESSION_VIJXTA_START_REQUEST_SENT), "SESSION_VIJXTA_START_REQUEST_SENT");

        // wait for pipes to connect
        if (this.viJxtaDialog != null) {
            new Thread(new Runnable() {

                public void run() {
                    while (!ViJxtaCallControl.this.viJxtaDialog.isConnected()) {
                        try {
                            Thread.sleep(PIPE_CONNECT_SLEEP_TIME);
                            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                                LOG.info("Waiting for is connected ");
                            }
                        }
                        catch (InterruptedException ignored) {
                            //ignored
                        }
                    }
                }
            }, getClass().getName() + ":isConnected").start();
        }


        this.myJxtaView = p_view;

        this.myJxtaController = p_view.getControl();


        if (!this.viJxtaDialog.isConnected()) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("Constructor : ViJxtaDialog NOT connected!! Resources released. Module Shutdown.");
            }

        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("Constructor : ViJxtaDialog Connected!! Obtaining Resources. Starting Module");
            }

            // tests for presence of jmf.jar

            try {
                this.isJMFPresent = LocalDeviceMonitorControl.isJMFPresent();

            } catch (Exception x) {

                x.printStackTrace();

                isJMFPresent = false;

            } catch (Error e) {

                e.printStackTrace();

                isJMFPresent = false;

            }


            this.remoteMonitorControl = new RemoteMonitorControl(this);

            templateMessage = new DialogMessage(this.viJxtaDialog.getGroup()
                    .getPeerGroup().getPeerName(), null, this.viJxtaDialog.getGroup()
                    .getPeerGroup().getPeerGroupID().toString(), this.viJxtaDialog
                    .getGroup().getPeerGroup().getPeerGroupName());

            generalTimer = new Timer(true);

            roundTripTimerTask = new TimerTask() {

                public void run() {

                    localActionSendRoundTripRequest();
                }
            };


        }

    }//constructor


    /**
     * Returns the Dialog that this module is attched.
     */
    public Dialog getDialog() {

        return this.viJxtaDialog;
    }

    public void initSession() {
        if (this.isLocallyInitiated()) {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("locally inititated");
            }

            this.sendCommand(COMMAND_VIJXTA_INVITE_REQUEST, COMMAND_VIJXTA_INVITE_ACCEPT);

            this.setProtocolState(SESSION_VIJXTA_INVITE_REQUEST_SENT);

            generalTimer.schedule(roundTripTimerTask, this.ROUND_TRIP_INTERVAL, this.ROUND_TRIP_INTERVAL);
        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("NOT locally inititated");
            }
        }
    }

    public int getLocalVideoSourceType() {

        return localVideoSourceType;
    }


    public void setLocalVideoSourceType(int localVideoSourceType) {

        this.localVideoSourceType = localVideoSourceType;

        if (this.localVideoSourceType == LOCAL_DEVICE && this.isJMFPresent) {

            this.localMonitorControl = new LocalDeviceMonitorControl(this);

        } else if (this.localVideoSourceType == NETWORKED_DEVICE) {

            this.localMonitorControl = new NetworkedDeviceMonitorControl(this);

        } else if (this.localVideoSourceType == NO_DEVICE) {

            this.localMonitorControl = new NoDeviceMonitorControl(this);
        }

        this.localMonitorControl.addErrorListener(this);
    }

    public String getErrorMessage() {

        return this.errorMessage;
    }

    private void setErrorMessage(String errorMessage) {

        this.errorMessage = errorMessage;
    }


    public void deviceErrorEncountered(String errorString) {

        setErrorMessage(errorString);

        this.destroy();
    }

    /**
     * Executes the session protocol logic.
     * <p/>
     * basic order of operations:
     * SessionInviteRequest -->
     * <-- SessionInviteAccept
     * CallConfigRequest -->
     * <-- CallConfigAccept
     * CallStartRequest -->
     * <-- CallStartAccept
     * CallHangupRequest -->
     * <-- CallHangupAccept
     * SessionEndRequest -->
     * <-- SessionEndAccept
     * <p/>
     * The Peer to Send an invitation controls the session start and call start.
     * After this either peer may place the call on hold. Once on hold only the
     * peer to place the call on hold may resume the call. CallHangup may be
     * performed by either peer. Quality elements are added to startcall
     * commands. Video data messages are not squenced and do not require ack's.
     * Session logic is limited to two peers. Protocol state is used as an
     * event notification mechanism and to determine protocol command order.
     * This is not an adaptive protocol. Strict order of operations is observed
     */
    public void callControl(String command, DialogMessage msg) {

        if (COMMAND_VIJXTA_INVITE_REQUEST.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " + COMMAND_VIJXTA_INVITE_REQUEST +
                        " received");
            }

            if (getProtocolState() == SESSION_VIJXTA_DISCONNECTED) {

                setProtocolState(SESSION_VIJXTA_INVITE_REQUEST_RECEIVED);

                sendCommand(COMMAND_VIJXTA_INVITE_ACCEPT);

                setProtocolState(SESSION_VIJXTA_INVITE_ACCEPT_SENT);

                // from here a config ui starts and we wait for a a config req

                generalTimer.schedule(roundTripTimerTask, this.ROUND_TRIP_INTERVAL, this.ROUND_TRIP_INTERVAL);

            } else {

                postProtocolStateException(SESSION_VIJXTA_DISCONNECTED);

            }


        } else if (COMMAND_VIJXTA_INVITE_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " + COMMAND_VIJXTA_INVITE_ACCEPT +
                        " received");
            }

            if (getProtocolState() == SESSION_VIJXTA_INVITE_REQUEST_SENT) {

                updateAckThread(command);

                setProtocolState(SESSION_VIJXTA_INVITE_ACCEPT_RECEIVED);

                // wait as user configs

            } else {

                postProtocolStateException(SESSION_VIJXTA_INVITE_REQUEST_SENT);

            }

        } else if (COMMAND_VIJXTA_CONFIG_REQUEST.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " + COMMAND_VIJXTA_CONFIG_REQUEST +
                        " received");
            }

            if (getProtocolState() == SESSION_VIJXTA_INVITE_ACCEPT_SENT) {

                parseConfigMessage(msg);

                setProtocolState(SESSION_VIJXTA_CONFIG_REQUEST_RECEIVED);

                localActionConfigRequestReceived(msg);


            } else {

                postProtocolStateException(SESSION_VIJXTA_INVITE_ACCEPT_SENT);

            }

        } else if (COMMAND_VIJXTA_CONFIG_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " + COMMAND_VIJXTA_CONFIG_ACCEPT +
                        " received");
            }

            if (getProtocolState() == SESSION_VIJXTA_CONFIG_REQUEST_SENT) {

                updateAckThread(command);

                parseConfigMessage(msg);

                setProtocolState(SESSION_VIJXTA_CONFIG_ACCEPT_RECEIVED);

                localActionConfigAcceptReceived(msg);

            } else {

                postProtocolStateException(SESSION_VIJXTA_CONFIG_REQUEST_SENT);

            }

        } else if (COMMAND_VIJXTA_START_REQUEST.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " + COMMAND_VIJXTA_START_REQUEST +
                        " received");
            }

            if (getProtocolState() == SESSION_VIJXTA_CONFIG_ACCEPT_SENT) {

                setProtocolState(SESSION_VIJXTA_START_REQUEST_RECEIVED);


            } else {

                postProtocolStateException(SESSION_VIJXTA_CONFIG_ACCEPT_SENT);


            }

        } else if (COMMAND_VIJXTA_START_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " + COMMAND_VIJXTA_START_ACCEPT +
                        " received");
            }

            if (getProtocolState() == SESSION_VIJXTA_START_REQUEST_SENT) {

                updateAckThread(command);

                setProtocolState(SESSION_VIJXTA_START_ACCEPT_RECEIVED);

                setProtocolState(SESSION_VIJXTA_INCALL);

            } else {

                postProtocolStateException(SESSION_VIJXTA_START_REQUEST_SENT);

            }

        } else if (COMMAND_VIJXTA_HOLD_REQUEST.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " + COMMAND_VIJXTA_HOLD_REQUEST +
                        " received");
            }


            if (getProtocolState() == SESSION_VIJXTA_INCALL) {


                setProtocolState(SESSION_VIJXTA_HOLD_REQUEST_RECEIVED);

                sendCommand(COMMAND_VIJXTA_HOLD_ACCEPT, null);

                setProtocolState(SESSION_VIJXTA_HOLD_ACCEPT_SENT);

                //place call on hold - we did not initiate the hold

                localActionHoldCall(false);

                setProtocolState(SESSION_VIJXTA_HOLDING);

            } else {

                postProtocolStateException(SESSION_VIJXTA_INCALL);

            }

        } else if (COMMAND_VIJXTA_HOLD_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " + COMMAND_VIJXTA_HOLD_ACCEPT +
                        " received");
            }

            if (getProtocolState() == SESSION_VIJXTA_HOLD_REQUEST_SENT) {

                updateAckThread(command);

                setProtocolState(SESSION_VIJXTA_HOLD_ACCEPT_RECEIVED);
                //place call on hold - we initiate the hold
                localActionHoldCall(true);

                setProtocolState(SESSION_VIJXTA_HOLDING);

            } else {

                postProtocolStateException(SESSION_VIJXTA_HOLD_REQUEST_SENT);

            }

        } else if (COMMAND_VIJXTA_RESUME_REQUEST.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " + COMMAND_VIJXTA_RESUME_REQUEST +
                        " received");
            }

            if (getProtocolState() == SESSION_VIJXTA_HOLDING) {

                setProtocolState(SESSION_VIJXTA_RESUME_REQUEST_RECEIVED);

                sendCommand(COMMAND_VIJXTA_RESUME_ACCEPT, null);

                setProtocolState(SESSION_VIJXTA_RESUME_ACCEPT_SENT);

                // resume call
                localActionResumeCall();

                setProtocolState(SESSION_VIJXTA_INCALL);

            } else {


                postProtocolStateException(SESSION_VIJXTA_HOLDING);

            }

        } else if (COMMAND_VIJXTA_RESUME_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " + COMMAND_VIJXTA_RESUME_ACCEPT +
                        " received");
            }

            if (getProtocolState() == SESSION_VIJXTA_RESUME_REQUEST_SENT) {

                updateAckThread(command);

                setProtocolState(SESSION_VIJXTA_RESUME_ACCEPT_RECEIVED);

                // resume call
                localActionResumeCall();

                setProtocolState(SESSION_VIJXTA_INCALL);

            } else {

                postProtocolStateException(SESSION_VIJXTA_RESUME_REQUEST_SENT);

            }

        } else if (COMMAND_VIJXTA_HANGUP_REQUEST.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " + COMMAND_VIJXTA_HANGUP_REQUEST +
                        " received");
            }


            if (getProtocolState() == SESSION_VIJXTA_INCALL) {

                setProtocolState(SESSION_VIJXTA_HANGUP_REQUEST_RECEIVED);

                sendCommand(COMMAND_VIJXTA_HANGUP_ACCEPT, null);

                setProtocolState(SESSION_VIJXTA_HANGUP_ACCEPT_SENT);

                // hangup call - stop transfering data
                localActionEndCall();

                setProtocolState(SESSION_VIJXTA_ENDED);

                if (roundTripTimerTask != null) {

                    roundTripTimerTask.cancel();

                }
            } else {

                postProtocolStateException(SESSION_VIJXTA_INCALL);

            }

        } else if (COMMAND_VIJXTA_HANGUP_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " + COMMAND_VIJXTA_HANGUP_ACCEPT +
                        " received");
            }


            if (getProtocolState() == SESSION_VIJXTA_HANGUP_REQUEST_SENT) {

                updateAckThread(command);

                setProtocolState(SESSION_VIJXTA_HANGUP_ACCEPT_RECEIVED);

                //hang up call
                localActionEndCall();

                sendCommand(COMMAND_VIJXTA_DISCONNECT_REQUEST, COMMAND_VIJXTA_DISCONNECT_ACCEPT);

                setProtocolState(SESSION_VIJXTA_DISCONNECT_REQUEST_SENT);


            } else {

                postProtocolStateException(SESSION_VIJXTA_HANGUP_REQUEST_SENT);


            }

        } else if (COMMAND_VIJXTA_DISCONNECT_REQUEST.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " +
                        COMMAND_VIJXTA_DISCONNECT_REQUEST + " received");
            }

            if (getProtocolState() == SESSION_VIJXTA_ENDED) {

                setProtocolState(SESSION_VIJXTA_DISCONNECT_REQUEST_RECEIVED);

                sendCommand(COMMAND_VIJXTA_DISCONNECT_ACCEPT, null);

                setProtocolState(SESSION_VIJXTA_DISCONNECT_ACCEPT_SENT);

                //disconnect from host - close window no more calls from this tab

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("Disconnecting");

                }

                localActionEndSession();

                setProtocolState(SESSION_VIJXTA_DISCONNECTED);

            } else {
                // the other side is going down... kill session
                // this is the non graceful shutdown withthe protocol being in any state
                setProtocolState(SESSION_VIJXTA_DISCONNECT_REQUEST_RECEIVED);

                sendCommand(COMMAND_VIJXTA_DISCONNECT_ACCEPT, null);

                setProtocolState(SESSION_VIJXTA_DISCONNECT_ACCEPT_SENT);

                //disconnect from host - close window no more calls from this tab
                localActionEndSession();

                setProtocolState(SESSION_VIJXTA_DISCONNECTED);


                postProtocolStateException(SESSION_VIJXTA_ENDED);
                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {


                    LOG.info("Ungraceful disconnect received...Going Down");
                }
            }

        } else if (COMMAND_VIJXTA_DISCONNECT_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " +
                        COMMAND_VIJXTA_DISCONNECT_ACCEPT + " received");
            }

            if (getProtocolState() == SESSION_VIJXTA_ENDED) {

                updateAckThread(command);

                setProtocolState(SESSION_VIJXTA_DISCONNECT_ACCEPT_RECEIVED);

                //disconnect from host - close window no more calls from this tab
                localActionEndSession();

                setProtocolState(SESSION_VIJXTA_DISCONNECTED);

            } else {
                // the other side is going down... kill session
                // TODO:
                postProtocolStateException(SESSION_VIJXTA_ENDED);

            }
        } else if (COMMAND_VIJXTA_ROUND_TRIP_REQUEST.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " +
                        COMMAND_VIJXTA_ROUND_TRIP_REQUEST + " received");
            }

            localActionRoundTripRequestReceived(msg);

        } else if (COMMAND_VIJXTA_ROUND_TRIP_ACCEPT.equals(command)) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("callControl : " +
                        COMMAND_VIJXTA_ROUND_TRIP_ACCEPT + " received");
            }

            localActionRoundTripAcceptReceived(msg);
        }


    }//call control

    private void localActionRoundTripRequestReceived(DialogMessage msg) {

        HashMap<String, MessageElement> map = new HashMap<String, MessageElement>();

        map.put(TAG_ROUND_TRIP_REQUEST_TIME, msg.getMessageElement(TAG_ROUND_TRIP_REQUEST_TIME));

        this.sendCommand(COMMAND_VIJXTA_ROUND_TRIP_ACCEPT, null, map);

    }

    private void localActionRoundTripAcceptReceived(DialogMessage msg) {

        MessageElement me = msg.getMessageElement(TAG_ROUND_TRIP_REQUEST_TIME);

        if (me != null) {

            String roundTripString = me.toString();

            if (roundTripString != null) {

                try {

                    long originTime = Long.valueOf(roundTripString).longValue();

                    long destinationTime = System.currentTimeMillis();

                    this.setRoundTripTime(destinationTime - originTime);

                } catch (NumberFormatException nfx) {

                    nfx.printStackTrace();
                }
            }
        }
    }

    private void localActionSendRoundTripRequest() {


        HashMap<String, MessageElement> elementMap = new HashMap<String, MessageElement>();

        String originTimeString = String.valueOf(System.currentTimeMillis());

        StringMessageElement originTimeElement = new StringMessageElement(
                TAG_ROUND_TRIP_REQUEST_TIME, originTimeString, null);

        elementMap.put(TAG_ROUND_TRIP_REQUEST_TIME, originTimeElement);

        this.sendCommand(COMMAND_VIJXTA_ROUND_TRIP_REQUEST, null, elementMap);

    }


    private void setRoundTripTime(long roundTrip) {

        this.roundTrip = roundTrip;
    }

    public long getRoundTripTime() {

        return this.roundTrip;
    }

    private void postProtocolStateException(int expectedProtocolState) {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("expected " +
                    this.getProtocolStateString(expectedProtocolState) +
                    " - got " +
                    this.getProtocolStateString(this.getProtocolState()));

        }
    }

    /**
     * Called on session end.
     */
    public void destroy() {


        if (this.getProtocolState() != SESSION_VIJXTA_DISCONNECTED) {

            this.localActionMessageGoneUnAcknowledged();
        }

        if (receiveTimeoutThread != null) {

            receiveTimeoutThread.stopThread();

            receiveTimeoutThread = null;
        }

        this.stopTransmitReceive();

        this.stopMonitors();

        if (roundTripTimerTask != null) {

            roundTripTimerTask.cancel();

        }

        if (generalTimer != null) {

            generalTimer.cancel();
        }
    }


    private String getProtocolStateString(int sessionState) {

        return sessionStateTable.get(new Integer(sessionState));
    }

    /**
     * message has been received... determine if its the correct response
     */
    protected void updateAckThread(String command) {
        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("Checking AckThread");
        }

        if (messageAckThread != null) {

            messageAckThread.setMessageAcknowledged(command);

            if (messageAckThread.isThreadEnded()) {

                messageAckThread = null;
            }
        } else {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("MessageAckThread is null!!");
            }

        }
    }


    protected void localActionConfigRequestReceived(DialogMessage msg) {
        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("localActionConfigRequestReceived");
        }
        if (configWaitTimerTask != null) {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("localActionConfigRequestReceived: cancel configWaitTimerTask");
            }
            configWaitTimerTask.cancel();
        }

        if (this.isConfigured()) {

            localActionSendConfigAccept();
        } else {
            //we wait for user to configure
        }
    }

    protected void localActionConfigAcceptReceived(DialogMessage msg) {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("localActionConfigRequestReceived");
        }


    }

    /*
    *  XML elements attached:
    *      -compression(quality) level that remote peer is sending.
    *      -format size (ie: 360x240)
    *      -image format type (ie: jpeg, png, bmp)
    */
    protected void parseConfigMessage(DialogMessage msg) {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("ConfigMessage Received: \n" + msg.toString());
        }


        MessageElement me;

        me = msg.getMessageElement(TAG_IMAGE_FORMAT_SIZE_ELEMENT);

        if (me != null) {

            String remoteFormatSizeString = me.toString();

            if (remoteFormatSizeString != null) {

                getRemoteMonitorControl().setFormatSize(ViJxtaUtil.stringToDimension(remoteFormatSizeString));

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("Remote Format Size Received: " +
                            remoteFormatSizeString);
                }
            }
        }


        me = msg.getMessageElement(TAG_IMAGE_QUALITY_ELEMENT);


        if (me != null) {

            String remoteImageQualityString = me.toString();

            if (remoteImageQualityString != null) {

                getRemoteMonitorControl().setImageCompression(Integer.valueOf(remoteImageQualityString).intValue());

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("Remote Image Quality Received: " +
                            remoteImageQualityString);
                }
            }
        }


        me = msg.getMessageElement(TAG_IMAGE_FORMAT_TYPE_ELEMENT);

        if (me != null) {

            String remoteFormatTypeString = me.toString();

            if (remoteFormatTypeString != null) {

                getRemoteMonitorControl().setFormatType(remoteFormatTypeString);

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("Remote Format Type Received: " +
                            remoteFormatTypeString);
                }
            }
        }

        me = msg.getMessageElement(TAG_IMAGE_TRANSMIT_ELEMENT);

        if (me != null) {

            String remoteImageTransmitString = me.toString();

            if (remoteImageTransmitString != null) {

                this.setRemotePeerTransmit(Boolean.valueOf(remoteImageTransmitString).booleanValue());
                // weather we receive is not determined by their choice to 
                //transmit, but instead by our choice to receive their
                // transmition; hence this is determined by the local user
                //this.getRemoteMonitorControl ().setReceive (Boolean.valueOf (remoteImageTransmitString).booleanValue ());

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("Remote Image Transmit Received: " +
                            remoteImageTransmitString);
                }
            }
        }

        me = msg.getMessageElement(TAG_IMAGE_RECEIVE_ELEMENT);

        if (me != null) {

            MessageElement remoteImageReceiveElement;
            remoteImageReceiveElement = me;

            String remoteImageReceiveString = remoteImageReceiveElement.toString();

            if (remoteImageReceiveString != null) {

                this.setRemotePeerReceive(Boolean.valueOf(remoteImageReceiveString).booleanValue());

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("Remote Image Receive Received: " +
                            remoteImageReceiveString);
                }
            }
        }


    }


    public void setRemotePeerTransmit(boolean remotePeerTransmit) {

        this.remotePeerTransmit = remotePeerTransmit;
    }

    public void setRemotePeerReceive(boolean remotePeerReceive) {

        this.remotePeerReceive = remotePeerReceive;
    }

    public boolean isRemotePeerTransmit() {

        return this.remotePeerTransmit;
    }

    public boolean isRemotePeerReceive() {

        return this.remotePeerReceive;
    }


    protected HashMap<String, MessageElement> getConfigElements() {

        HashMap<String, MessageElement> elementMap = new HashMap<String, MessageElement>();

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("LocalPeer Transmit ? " +
                    this.getLocalMonitorControl().isTransmit());
        }

        if (this.getLocalMonitorControl().isTransmit()) {

            String imageQualityString = String.valueOf(getLocalMonitorControl().getImageCompression());

            StringMessageElement imageQualityElement = new StringMessageElement(
                    TAG_IMAGE_QUALITY_ELEMENT, imageQualityString, null);

            elementMap.put(TAG_IMAGE_QUALITY_ELEMENT, imageQualityElement);


            String imageFormatSizeString = String.valueOf(ViJxtaUtil.
                    dimensionToString(getLocalMonitorControl().getFormatSize()));

            StringMessageElement imageFormatSizeElement = new StringMessageElement(
                    TAG_IMAGE_FORMAT_SIZE_ELEMENT, imageFormatSizeString, null);

            elementMap.put(TAG_IMAGE_FORMAT_SIZE_ELEMENT, imageFormatSizeElement);


            String imageFormatTypeString = String.valueOf(getLocalMonitorControl().getMimeType());

            StringMessageElement imageFormatTypeElement = new StringMessageElement(
                    TAG_IMAGE_FORMAT_TYPE_ELEMENT, imageFormatTypeString, null);

            elementMap.put(TAG_IMAGE_FORMAT_TYPE_ELEMENT, imageFormatTypeElement);

        } else {
            LOG.info("AddElement NOT TRANSMITTING");
        }

        String imageReceiveString = String.valueOf(getRemoteMonitorControl().isReceive());

        StringMessageElement imageReceiveElement = new StringMessageElement(
                TAG_IMAGE_RECEIVE_ELEMENT, imageReceiveString, null);

        elementMap.put(TAG_IMAGE_RECEIVE_ELEMENT, imageReceiveElement);

        boolean transmit = false;

        /** fix for issue#236, check for JMF only necessary when local device
         * is in use. for internet cams a JMF check can be skipped.
         */
        if (this.localVideoSourceType == LOCAL_DEVICE) {

            transmit = (this.isJMFPresent && getLocalMonitorControl().isTransmit());

        } else if (this.localVideoSourceType == NETWORKED_DEVICE) {

            transmit = true; //xxx there should be checking here for errors

        } else if (this.localVideoSourceType == NO_DEVICE) {

            transmit = false;

        }

        String imageTransmitString = String.valueOf(transmit);

        StringMessageElement imageTransmitElement = new StringMessageElement(
                TAG_IMAGE_TRANSMIT_ELEMENT, imageTransmitString, null);

        elementMap.put(TAG_IMAGE_TRANSMIT_ELEMENT, imageTransmitElement);

        return elementMap;
    }


    /**
     * Command SendStartRquest to remote.
     * XML elements attached:
     * -compression(image quality) level that remote peer is sending.
     * -format size (ie: 360x240)
     * -image format type (ie: jpeg, png, bmp)
     */
    protected void localActionSendConfigRequest() {

        HashMap<String, MessageElement> configMap = getConfigElements();

        sendCommand(COMMAND_VIJXTA_CONFIG_REQUEST,
                COMMAND_VIJXTA_CONFIG_ACCEPT,
                configMap);


        setProtocolState(SESSION_VIJXTA_CONFIG_REQUEST_SENT);

    }


    protected void localActionSendConfigAccept() {

        HashMap<String, MessageElement> configMap = getConfigElements();

        sendCommand(COMMAND_VIJXTA_CONFIG_ACCEPT,
                null,
                configMap);

        setProtocolState(SESSION_VIJXTA_CONFIG_ACCEPT_SENT);

    }


    /**
     * Command StartRquest received from remote
     * XML elements attached:
     * -compression(image quality) level that remote peer is sending.
     * -format size (ie: 360x240)
     * -image format type (ie: jpeg, png, bmp)
     * SendAccept back to remote peer with attached elements
     */
    protected void localActionStartRequestReceived(DialogMessage msg) {


    }


    /**
     * Sets if this sesseion is to be persisted
     * TODO
     */
    public void setPersistSession(boolean persist, String fileName) {

        this.sessionPersisted = persist;

        this.sessionFileName = fileName;

    }

    /**
     *
     */
    public boolean isSessionPersisted() {

        return this.sessionPersisted;
    }

    /**
     *
     */
    public String getSessionPersistFileName() {

        return this.sessionFileName;
    }

    /**
     *
     */
    public void viewActionPlaceCall() {

        sendCommand(COMMAND_VIJXTA_START_REQUEST, COMMAND_VIJXTA_START_ACCEPT);

        setProtocolState(SESSION_VIJXTA_START_REQUEST_SENT);

        this.setCallStartTime();
    }

    public void viewActionEndCall() {

        if (roundTripTimerTask != null) {

            roundTripTimerTask.cancel();

        }

        sendCommand(COMMAND_VIJXTA_HANGUP_REQUEST, COMMAND_VIJXTA_HANGUP_ACCEPT);

        setProtocolState(SESSION_VIJXTA_HANGUP_REQUEST_SENT);

        this.localActionEndCall();
    }

    public boolean isLocallyInitiated() {

        return this.locallyInitiated;
    }


    private void setConfigured(boolean configured) {

        this.configured = configured;
    }

    private boolean isConfigured() {

        return this.configured;
    }

    public void viewActionConfigured() {

        this.setConfigured(true);

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("viewActionConfigured");
        }

        if (this.isLocallyInitiated()) {

            this.localActionSendConfigRequest();

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("viewActionConfigured: Locally initited");
            }

        } else {


            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("viewActionConfigured: NOT Locally initited");
            }

            if (this.getProtocolState() == ViJxtaCallControl.SESSION_VIJXTA_CONFIG_REQUEST_RECEIVED) {

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("viewActionConfigured: config request has been received");
                }

                localActionSendConfigAccept();

            } else {

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("viewActionConfigured: config request has NOT been received. starting timer");
                }

                /** we'll wait 40 second for a config to come from the controlling
                 *  peer. if none arrives then we go down. */
                configWaitTimerTask = new TimerTask() {

                    public void run() {
                        if (getProtocolState() ==
                                ViJxtaCallControl.SESSION_VIJXTA_CONFIG_REQUEST_RECEIVED) {
                            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                                LOG.info("configWaitTimerTask: CONFIG REQUEST Received");
                            }
                            localActionSendConfigAccept();
                        } else {
                            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                                LOG.info("configWaitTimerTask: Timeout wating for remote config");
                            }
                            destroy();
                        }
                    }
                };

                generalTimer.schedule(configWaitTimerTask, CONFIG_WAIT_TIMOUT);

            }


        }
    }

    /**
     * Command EndCall reveived from remote peer
     */
    private void localActionEndCall() {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("localActionEndCall");
        }

        setCallEndTime();

        if (receiveTimeoutThread != null) {

            receiveTimeoutThread.stopThread();

            receiveTimeoutThread = null;
        }

        this.stopTransmitReceive();

        this.stopMonitors();
    }

    private void stopMonitors() {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("stopMonitors");
        }

        if (localMonitorControl != null) {

            getLocalMonitorControl().stopMonitor();

            getLocalMonitorControl().releaseHardware();
        }

        if (remoteMonitorControl != null) {

            getRemoteMonitorControl().stopMonitor();

            getRemoteMonitorControl().releaseHardware();
        }
    }

    private void stopTransmitReceive() {

        if (this.getLocalMonitorControl() != null) {

            getLocalMonitorControl().stopTransmit();
        }

        if (remoteMonitorControl != null) {

            getRemoteMonitorControl().stopReceive();
        }
    }


    /**
     * Sets the time in ms this call started
     */
    protected void setCallStartTime() {


        this.callStartTime = System.currentTimeMillis();
    }

    /**
     * Sets the time in ms this call ended
     */
    protected void setCallEndTime() {

        this.callEndTime = System.currentTimeMillis();
    }

    /**
     * Command HoldCall reveived from remote
     */
    private void localActionHoldCall(boolean myHold) {

        getLocalMonitorControl().pauseMonitor();

        getRemoteMonitorControl().pauseMonitor();

    }


    /**
     * Command ResumeCall received from remote
     */
    private void localActionResumeCall() {

        getLocalMonitorControl().resumeMonitor();

        getRemoteMonitorControl().resumeMonitor();
    }

    /**
     * Command EndSession received from remote
     */
    private void localActionEndSession() {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("localActionEndSession");
        }

        if (receiveTimeoutThread != null) {

            receiveTimeoutThread.stopThread();

            receiveTimeoutThread = null;
        }

        this.stopTransmitReceive();

        this.stopMonitors();

    }


    /**
     * Called from UI signaling user accepts call invitation
     */
    public void viewActionAcceptCall() {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("viewActionAcceptCall");
        }

        sendCommand(COMMAND_VIJXTA_START_ACCEPT, null);

        setProtocolState(SESSION_VIJXTA_START_ACCEPT_SENT);

        setProtocolState(SESSION_VIJXTA_INCALL);
    }

    /**
     * Called from IU signaling user wishes to place call on hold.
     */
    public void viewActionHoldCall() {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("viewActionHoldCall");
        }

        sendCommand(COMMAND_VIJXTA_HOLD_REQUEST, COMMAND_VIJXTA_HOLD_ACCEPT);

        setProtocolState(SESSION_VIJXTA_HOLD_REQUEST_SENT);

    }

    /**
     * Called from IU signaling user wishes to hangup call.
     */
    public void viewActionHangupCall() {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("viewActionHangupCall");
        }

        sendCommand(COMMAND_VIJXTA_HANGUP_REQUEST, COMMAND_VIJXTA_HANGUP_ACCEPT);

        setProtocolState(SESSION_VIJXTA_HANGUP_REQUEST_SENT);

        this.setCallEndTime();

        this.stopTransmitReceive();

        this.stopMonitors();
    }

    /**
     * Called from IU signaling user wishes to resume a call previsously placed
     * on hold.
     */
    public void viewActionResumeCall() {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("viewActionResumeCall");
        }

        sendCommand(COMMAND_VIJXTA_RESUME_REQUEST, COMMAND_VIJXTA_RESUME_ACCEPT);

        setProtocolState(SESSION_VIJXTA_RESUME_REQUEST_SENT);

    }


    private void sendCommand(String command) {

        sendCommand(command, null, null);
    }

    private void sendCommand(String command, String ackCommand) {

        sendCommand(command, ackCommand, null);
    }

    /**
     * Sends a command on the vijxta dialog pipe. This does not change the
     * Protocol state. We have to remember to do this ourselves.
     */
    private void sendCommand(String command, String ackCommand, HashMap<String, MessageElement> elementMap) {

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("sendCommand : " + command);
        }


        DialogMessage msg = (DialogMessage) templateMessage.clone();

        StringMessageElement commandElement = new StringMessageElement(
                TAG_SESSION_COMMAND, command, null);

        msg.addMessageElement(TAG_SESSION_COMMAND, commandElement);

        if (elementMap != null) {

            for (String elementName : elementMap.keySet()) {

                MessageElement element = elementMap.get(elementName);

                msg.addMessageElement(elementName, element);

            }
        }

        if (ackCommand != null) {

            this.messageAckThread = new MessageAckThread(ackCommand);

            this.messageAckThread.start();

            this.messageAckThread.waitForStart();
        }

        this.viJxtaDialog.dispatch(msg);

    }


    /**
     * This method updates the current protocol state. Also signals the UI
     * of a protocol state change.
     */
    private void setProtocolState(int protocolState) {

        this.protocolState = protocolState;

        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info("ProtocolStateChanged to " +
                    this.sessionStateTable.get(new Integer(protocolState)));
        }

        this.viJxtaView.protocolStateChanged(protocolState);
    }

    /**
     * Returns the protocol state
     */
    public int getProtocolState() {

        return this.protocolState;
    }

    /**
     * Returns the current time in milliseconds to wait for a message respose
     * before session shutdown.
     */
    public long getMessageAckTimeout() {

        return (messageAckTimeout > MINIMUM_MESSAGE_ACK_TIMEOUT) ?
                messageAckTimeout : DEFAULT_MESSAGE_ACK_TIMEOUT;
    }

    public void setMessageAckTimeout(long timeInMilliseconds) {

        if (timeInMilliseconds > MINIMUM_MESSAGE_ACK_TIMEOUT) {

            this.messageAckTimeout = timeInMilliseconds;
        }
    }

    /**
     * Message ack thread has timed out and no message ack has been received.
     * End session.
     */
    protected void localActionMessageGoneUnAcknowledged() {
        if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
            LOG.info(" localActionMessageGoneUnAcknowledged ");
        }

        localActionEndSession();

        /** Normally we'd just call sendcommand(END_SESSION) and wait for a
         *  end END_SEESION_ACCEPT then go down gracefully. Since we aren't
         *  receiving command messages we choose to just exit hard. */

        sendCommand(COMMAND_VIJXTA_DISCONNECT_REQUEST);

        this.setProtocolState(SESSION_VIJXTA_DISCONNECTED);
    }

    /**
     *
     */
    public RemoteMonitorControl getRemoteMonitorControl() {

        return this.remoteMonitorControl;
    }

    /**
     *
     */
    public DeviceMonitorControl getLocalMonitorControl() {

        return this.localMonitorControl;
    }


    /**
     * A templete DialogMessage is already constructed. All we need do is call
     * this method and clone. This will cleanup the code for message production
     * as well offer the possibility of a speedup in message creation.
     *
     * @return templeted DialogMessage
     */
    public DialogMessage getNewTemplateMessage() {

        return (DialogMessage) this.templateMessage.clone();
    }

    public String getOriginator() {

        if (this.getRemoteMonitorControl() == null) {
            LOG.info("remoteMonitorCOntrol is null");
        }
        if (getRemoteMonitorControl().getOriginator() == null) {
            LOG.info("getOriginator is null");
        }

        return getRemoteMonitorControl().getOriginator();
    }

    /**
     * Returns the system time in ms this call was started. Only valid after
     * call ha started
     */
    public long getCallStartTime() {

        return callStartTime;
    }

    /**
     * Returns a formated string of the call start time
     */
    public String getCallStartTimeString() {

        return new Date(callStartTime).toString();
    }

    /**
     * Returns the system time is ms this call ended. Is only valid after call
     * has ended.
     */
    public long getCallEndTime() {

        return callEndTime;
    }

    /**
     * Returns a formated string of hte call end time.
     */
    public String getCallEndTimeString() {

        return ((callEndTime != 0) ? new Date(callEndTime).toString() : "");
    }

    /**
     * Returns the time elapsed in ms from call start time. Only valid after
     * call has started
     */
    public long getCallElapsedTime() {

        return callElapsedTime = System.currentTimeMillis() - getCallStartTime();
    }

    /**
     * Returns formated string of the call elapsed time
     */
    public String getCallElapseTimeString() {


        return formatElapsedTime(getCallElapsedTime());
    }

    /**
     * Returns a string formated to represent hours:minutes:seconds of the time
     * elapsed since call start.
     */
    protected String formatElapsedTime(long elapsedTime) {

        long tmp = elapsedTime;

        long hours = tmp / HOUR;

        tmp = tmp - (hours * HOUR);

        long minutes = tmp / MINUTE;

        tmp = tmp - (minutes * MINUTE);

        long seconds = tmp / SECOND;

        String timeString;

        timeString = String.valueOf(hours) + TIME_SEPARATOR;

        if (minutes < 10) {
            timeString = timeString + "0";
        }
        timeString = timeString + String.valueOf(minutes) + TIME_SEPARATOR;

        if (seconds < 10) {
            timeString = timeString + "0";
        }
        timeString = timeString + String.valueOf(seconds);

        return timeString;
    }


    /**
     * After a message is sent this thread is instantiated, started and will
     * wait till it's lock is notified of an incoming message or till a timeout
     * is reached. The idea being each commmand message dispatched will return
     * an accept response. If that response is received then +1 if not this thread
     * will notify us and shutdown the session as gracefully as possible. A new
     * Thread is started for each command message sent.
     * TODO
     * Better option then creating a new thread for each command. Reuse of a single
     * thread maybe.
     */
    class MessageAckThread extends Thread {

        private final AckWaitLock ackWaitLock;

        private String commandName = null;

        private boolean threadEnded = false;

        private boolean threadStarted = false;

        public MessageAckThread(String commandName) {

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("MessageAckThread waiting for command " + commandName);
            }

            ackWaitLock = new AckWaitLock();

            this.commandName = commandName;

            this.setPriority(Thread.MIN_PRIORITY);

            //this.setDaemon (true);
        }

        public void waitForStart() {
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("MessageAckThread waiting for threadstart");
            }
            while (!this.threadStarted) {

                try {
                    Thread.sleep(100);
                } catch (InterruptedException ix) {
                    ix.printStackTrace();
                }
            }
            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("MessageAckThread thread started");
            }

        }

        public void run() {

            threadStarted = true;

            if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                LOG.info("MessageAckThread : Run ");
            }

            try {

                synchronized (ackWaitLock) {
                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("MessageAckThread : waiting!!!!!");
                    }
                    ackWaitLock.wait(getMessageAckTimeout());
                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("MessageAckThread : DONE waiting!!!!!");
                    }
                }
            } catch (InterruptedException ix) {

                ix.printStackTrace();
            }

            synchronized (ackWaitLock) {

                if (!ackWaitLock.isAcknowledged()) {
                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("MessageAckThread : " + this.commandName +
                                " command NOT acknowledged");
                    }
                    ViJxtaCallControl.this.localActionMessageGoneUnAcknowledged();
                }
            }

            this.threadEnded = true;


        }

        /**
         * Avoids a race condition... needs to be reworked.
         */
        public boolean isThreadEnded() {

            while (!this.threadEnded) {

                try {
                    Thread.sleep(100);
                } catch (InterruptedException ix) {
                    ix.printStackTrace();
                }
            }

            return this.threadEnded;
        }

        /**
         * A message was received and this thread is alerted.
         */
        public void setMessageAcknowledged(String commandName) {

            synchronized (ackWaitLock) {

                if (this.commandName.equals(commandName)) {

                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("MessageAckThread : Received RIGHT Ack " +
                                commandName + " for " + this.commandName +
                                " : Command Ack");
                    }

                    ackWaitLock.setAcknowledged(true);
                } else {

                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info("MessageAckThread : Received WRONG Ack " +
                                commandName + " for " + this.commandName +
                                " : Command Ack");
                    }
                }

                ackWaitLock.notifyAll();

            }

        }

        class AckWaitLock {

            private boolean acknowledged = false;

            public boolean isAcknowledged() {

                return acknowledged;
            }

            public void setAcknowledged(boolean acked) {

                this.acknowledged = acked;

                if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                    LOG.info("AckWaitLock : SetAcknowldge " + acknowledged);
                }
            }

        }
    }

    /**
     * Because we push the video payload through without periodic ack's we need
     * a thread to count a timeout in case the other side goes down.  Time out
     * of 40 seconds while IN CALL should be enough for any relay to catch up.
     * A more elegant solution is needed though i'd still like to keep it simple.
     * With reliable pipes SIP type acks seem too much
     */
    class ReceivedMessageTimeoutThread extends Thread {

        public static final int SLEEP_TIME = 1000;

        public static final long DEFAULT_PAYLOAD_MESSAGE_TIMEOUT = 1000 * 40;

        public static final long MINIMUM_PAYLOAD_MESSAGE_TIMEOUT = 1000 * 40;

        private long payloadMessageTimeout = DEFAULT_PAYLOAD_MESSAGE_TIMEOUT;

        private boolean run = true;

        public ReceivedMessageTimeoutThread() {

            setPriority(Thread.MIN_PRIORITY);

            //this.setDaemon (true);

        }

        public long getPayloadMessageTimeout() {

            return payloadMessageTimeout;
        }

        public void setPayloadMessageTimeout(long payloadMessageTimeout) {

            if (payloadMessageTimeout >= MINIMUM_PAYLOAD_MESSAGE_TIMEOUT) {

                this.payloadMessageTimeout = payloadMessageTimeout;
            }
        }

        public void stopThread() {

            run = false;
        }

        public void run() {

            while (run) {

                try {

                    Thread.sleep(ViJxtaCallControl.SECOND);
                } catch (InterruptedException ix) {
                    ix.printStackTrace();
                }

                if (getProtocolState() == ViJxtaCallControl.SESSION_VIJXTA_HOLDING) {
                    continue;
                }

                long lastMessageReceived = 0;

                if (remoteMonitorControl != null) {

                    lastMessageReceived = getRemoteMonitorControl().getTimeOfLastMessage();
                }

                long currentTime = System.currentTimeMillis();

                if (getProtocolState() == ViJxtaCallControl.SESSION_VIJXTA_INCALL &&
                        (currentTime - lastMessageReceived) > getPayloadMessageTimeout()) {

                    if (Logging.SHOW_INFO && LOG.isLoggable(Level.INFO)) {
                        LOG.info(" diff " + (currentTime - lastMessageReceived) +
                                " > " + getPayloadMessageTimeout());
                    }

                    ViJxtaCallControl.this.localActionMessageGoneUnAcknowledged();
                }
            }

        }

    }

    class MyLock {

        private boolean locked = false;

        public boolean isLocked() {
            return locked;
        }

        public void setLocked(boolean locked) {
            this.locked = locked;
        }
    }

}
